package com.gxzy.util;

import com.alibaba.fastjson.JSONObject;
import com.gxzy.config.FinalPoolCfg;
import com.gxzy.domain.view.FileInfoUrlView;
import com.gxzy.domain.view.LoginInfoView;

import com.gxzy.repo.view.FileInfoUrlViewRepo;
import com.gxzy.util.rep.RepCode;
import com.gxzy.util.rep.RepUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

import static com.gxzy.config.FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME;

@Component
public class BaseServiceUtil {
    private final Logger logger = LogManager.getLogger(this.getClass());

    // 单次浏览统计缓存过期时间
    public static final long READIND_INVALID = 12 * 60 * 60L;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private FileInfoUrlViewRepo fileInfoUrlViewRepo;

    @Value("${mconfig.redis.accessTokenTimeout}")
    private long akTimeout;

    /**
     * 更新缓存信息
     *
     * @param request
     * @param key
     * @param value
     * @return
     */
    public boolean setAttr(HttpServletRequest request, String key, Object value) {
        return setAttr(request, key, value, akTimeout);
    }

    /**
     * 更新缓存信息，可指定有效时长
     *
     * @param request
     * @param key
     * @param value
     * @param akTimeout
     * @return
     */
    public boolean setAttr(HttpServletRequest request, String key, Object value, long akTimeout) {
        request.getSession().setAttribute(key, value);
        boolean set = redisUtil.set(key, value, akTimeout);

        logger.info("redis save -> key: " + key + " -> value: " + value + " -> timeOut: " + akTimeout);
        return set;
    }

    /**
     * 从缓存中读取信息
     *
     * @param request
     * @param key
     * @return
     */
    public Object getAttr(HttpServletRequest request, String key, boolean remove) {
        Object value = request.getSession().getAttribute(key);

        if (value == null) {
            value = redisUtil.get(key);
        }

        if (remove) {
            request.getSession().removeAttribute(key);
            redisUtil.remove(key);
        }

        logger.info("redis get -> key: " + key + " -> value: " + value + " -> remove: " + remove);
        return value;
    }

    /**
     * 保存登陆状态
     *
     * @param request
     * @param response
     * @param loginInfo
     */
    public boolean setSessionLogin(HttpServletRequest request, HttpServletResponse response, LoginInfoView loginInfo) {
        /* 授权 */
        String accessToken = RandomUtil.dao.createAccessToken(FinalPoolCfg.SECRET_KEY);
        accessToken = RandomUtil.dao.urlEncodeAK(accessToken);

        response.setHeader(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME, accessToken);

        /* 保存登录信息 */
        HttpSession session = request.getSession();
        session.setAttribute(FinalPoolCfg.SESSION_LOGININFO_NAME, loginInfo);
        session.setAttribute(FinalPoolCfg.SESSION_RESCODE_NAME, RepCode.CODE_SUCCESS);
        session.setAttribute(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME, accessToken);

        logger.info("/* 保存登陆信息 -> " + loginInfo.getLogin().getUsername() + " -> " + RepCode.CODE_SUCCESS + " -> " + accessToken + "*/");
        logger.info("/* loginInfo ->  " + loginInfo + "*/");

        return redisUtil.set(accessToken, loginInfo, akTimeout);
    }

    /**
     * 更新用户登录信息
     *
     * @param request
     * @param loginInfo
     * @return
     */
    public boolean setSessionLogin(HttpServletRequest request, LoginInfoView loginInfo) {
        String accessToken = getSessionAK(request);

        if (!CheckUtil.dao.isAccessToken(accessToken)) {
            return false;
        }

        /* 保存登录信息 */
        HttpSession session = request.getSession();
        session.setAttribute(FinalPoolCfg.SESSION_LOGININFO_NAME, loginInfo);
        session.setAttribute(FinalPoolCfg.SESSION_RESCODE_NAME, RepCode.CODE_SUCCESS);
        session.setAttribute(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME, accessToken);

        logger.info("/* 更新登录信息 -> " + loginInfo.getLogin().getUsername() + " -> " + RepCode.CODE_SUCCESS + " -> " + accessToken + "*/");
        logger.info("/* loginInfo ->  " + loginInfo + "*/");

        return redisUtil.set(accessToken, loginInfo, akTimeout);
    }

    /**
     * 登出
     *
     * @param request
     */
    public void removeSossionLogin(HttpServletRequest request) {
        this.removeSossionLogin(request, null);
    }

    /**
     * 登出
     *
     * @param request
     * @param ak
     * @return
     */
    public void removeSossionLogin(HttpServletRequest request, String ak) {
        if (!CheckUtil.dao.isAccessToken(ak)) {
            ak = getSessionAK(request);
        }

        if (CheckUtil.dao.isAccessToken(ak)) {
            request.getSession().removeAttribute(FinalPoolCfg.SESSION_LOGININFO_NAME);
            request.getSession().removeAttribute(FinalPoolCfg.SESSION_RESCODE_NAME);
            request.getSession().removeAttribute(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME);
            redisUtil.remove(ak.trim());
            logger.info("/* 已登出 -> " + ak + "*/");
        }
    }

    /**
     * 获取授权码
     *
     * @param request
     * @return
     */
    public String getSessionAK(HttpServletRequest request) {
        HttpSession session = request.getSession();
        String ak = (String) session.getAttribute(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME);

        // 请求头中获取
        if (!CheckUtil.dao.isAccessToken(ak)) {
            ak = request.getHeader(HEADER_ACCESS_TOKEN_NAME);
        }

        // 请求路径中获取
        if (!CheckUtil.dao.isAccessToken(ak)) {
            ak = request.getParameter(FinalPoolCfg.HEADER_ACCESS_TOKEN_NAME);
            ak = RandomUtil.dao.urlEncodeAK(ak);
        }

        logger.info("/*req -> " + request.getRequestURL() + " -> " + request.getMethod() + " -> AK -> " + ak + "*/");

        return ak == null ? ak : ak.trim();
    }

    /**
     * 获取登陆信息
     *
     * @param request
     * @return
     */

    public LoginInfoView getSessionLogin(HttpServletRequest request) {
        return getSessionLogin(request, null);
    }

    public LoginInfoView getSessionLogin(String ak) {
        return getSessionLogin(null, ak);
    }

    public LoginInfoView getSessionLogin(HttpServletRequest request, String ak) {
        if (!CheckUtil.dao.isAccessToken(ak) && request != null) {
            ak = getSessionAK(request);
        }

        /* 取授权信息 */
        LoginInfoView loginInfo = null;
        if (request != null) {
            HttpSession session = request.getSession();
            loginInfo = (LoginInfoView) session.getAttribute(FinalPoolCfg.SESSION_LOGININFO_NAME);
        }

        /* 从缓存中取 */
        if (!CheckUtil.dao.isLoginInfoEnable(loginInfo) && CheckUtil.dao.isAccessToken(ak)) {
            Object res = redisUtil.get(ak.trim());
            if (res instanceof LoginInfoView) {
                loginInfo = (LoginInfoView) res;
            }
        }

//        // 本机测试
//        if (!CheckUtil.dao.isLoginInfoEnable(loginInfo)) {
//            Login login = Login.builder().build();
//            login.setId(0L);
//            login.setUsername("super");
//            User user = User.builder().build();
//            user.setId(0L);
//            user.setCommunity(1233631140447916032L);
//            Role role = Role.builder().name("超级管理员").build();
//            role.setId(1L);
//            loginInfo = LoginInfoView.builder()
//                    .login(login).user(user).role(role).build();
//        }

        if (CheckUtil.dao.isLoginInfoEnable(loginInfo)) {
            assert loginInfo != null;
            logger.info("访问者 -> " + loginInfo.getLogin().getUsername());
        }

        return loginInfo;
    }

    /**
     * 错误响应处理，公共方法
     *
     * @param request
     * @param errorCode
     * @return
     */
    public Object error(HttpServletRequest request, int errorCode) {
        logger.error("/*response error -> " + request.getRequestURL() + " -> " + request.getMethod() + " -> " + errorCode + "*/");

        String accept = request.getHeader("accept");
        if (accept.contains("text/html")) {
            ModelAndView mav = new ModelAndView();
            mav.setViewName("404");
            return mav;
        } else {
            return RepUtil.post(errorCode);
        }
    }

    /**
     * 流参数校验失败错误响应
     *
     * @param request
     * @param bindingResult
     * @return
     */
    public Object error(HttpServletRequest request, BindingResult bindingResult) {
        logger.error("/*request param error -> " + request.getRequestURL() + " -> " + request.getMethod()
                             + " -> " + bindingResult.getFieldError().getField() + " -> "
                             + bindingResult.getFieldError().getDefaultMessage() + "*/");

        String accept = request.getHeader("accept");
        if (accept.contains("text/html")) {
            ModelAndView mav = new ModelAndView();
            mav.setViewName("404");
            return mav;
        } else {
            return RepUtil.post(bindingResult.getFieldError());
        }
    }

    /**
     * 获取请求参数
     *
     * @param request
     * @param key     键名
     * @param isStr   是否转为字符串拼接.
     * @return
     */
    protected Object getParam(HttpServletRequest request, String key, boolean isStr) {
        Map<String, String[]> params = request.getParameterMap();

        Object res = "";
        if (params.containsKey(key)) {
            if (isStr) {
                String[] param = params.get(key);
                int index = param.length;

                for (String value : param) {
                    if (index == 1) {
                        res += value;
                    } else {
                        res += value + ",";
                    }
                    index--;
                }
            } else {
                res = params.get(key);
            }
        }

        logger.info("getParam " + key + ": " + res.toString());
        return res;
    }

    /**
     * 如果从请求参数中取不到,则继续从缓存参数中获取
     *
     * @param request 请求体
     * @param key     参数名
     * @param sts     时间戳
     * @param isStr   是否转为字符串
     * @return Object
     */
    protected Object getParam(HttpServletRequest request, String key, String sts, boolean isStr) {
        String res = getParam(request, key, isStr).toString();

        // 临时参数
        if (StringUtils.isEmpty(res.toString())) {
            Object attr = getAttr(request, "params_" + key + "_" + sts, true);
            if (attr != null) {
                res = attr.toString();
            }
        }

        logger.info("getParam " + key + ": " + res.toString());
        return res;
    }

    /**
     * 参数校验
     *
     * @param t
     * @param <T>
     * @return
     */
    public <T> boolean validator(T t) {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        Set<ConstraintViolation<T>> validate = validator.validate(t);
        logger.info("参数校验结果：" + validate);

        if (validate != null && validate.size() > 0) {
            Iterator<ConstraintViolation<T>> iterator = validate.iterator();
            ConstraintViolation<T> next = iterator.next();
            Map res = RepUtil.post(next);
            write(JSONObject.toJSONString(res));
            return false;
        } else {
            return true;
        }
    }

    /**
     * 写出数据
     *
     * @param res
     */
    public void write(String res) {
        HttpServletResponse response = ReqUtil.getResponse();
        try {
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(res);
        } catch (IOException e) {
            logger.error("错误：" + e.getMessage(), e);
        }
    }

    /**
     * 写出数据
     *
     * @param res
     */
    public void write(Map res) {
        write(JSONObject.toJSONString(res));
    }
}
